package cz.proks.easyhelpdesk.gwt.utils;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.ui.AcceptsOneWidget;

import cz.proks.easyhelpdesk.api.model.Category;
import cz.proks.easyhelpdesk.api.model.Filter;
import cz.proks.easyhelpdesk.api.model.Helpdesk;
import cz.proks.easyhelpdesk.api.model.HelpdeskMember;
import cz.proks.easyhelpdesk.api.model.HelpdeskRole;
import cz.proks.easyhelpdesk.api.model.Thread;
import cz.proks.easyhelpdesk.api.model.ThreadMember;
import cz.proks.easyhelpdesk.api.model.User;
import cz.proks.easyhelpdesk.gwt.Globals;
import cz.proks.easyhelpdesk.gwt.restapi.CategoryRestApi;
import cz.proks.easyhelpdesk.gwt.restapi.FilterRestApi;
import cz.proks.easyhelpdesk.gwt.restapi.HelpdeskMemberRestApi;
import cz.proks.easyhelpdesk.gwt.restapi.HelpdeskRestApi;
import cz.proks.easyhelpdesk.gwt.restapi.RestDomainCallback;
import cz.proks.easyhelpdesk.gwt.restapi.RestDomainListCallback;
import cz.proks.easyhelpdesk.gwt.restapi.ThreadMemberRestApi;
import cz.proks.easyhelpdesk.gwt.restapi.ThreadRestApi;
import cz.proks.easyhelpdesk.gwt.restapi.UserRestApi;
import cz.proks.easyhelpdesk.gwt.ui.widget.LoadingWidget;

public class ApplicationDataLoader {

    public static interface LoadCallback {
        void onLoadComplete(AcceptsOneWidget panel, EventBus eventBus);

        void onLoadFailed(AcceptsOneWidget panel, EventBus eventBus);
    }

    private enum State {
        USER_LOADED, HELPDESKS_LOADED, HELPDESK_MEMBERS_LOADED, HELPDESK_MEMBER_USER_LOADED, 
        HELPDESK_MEMBER_THREAD_MEMBERS_LOADED, THREAD_MEMBER_THREAD_LOADED, 
        HELPDESK_ROLES_LOADED, HELPDESK_CATEGORIES_LOADED, HELPDESK_FILTERS_LOADED, NONE, INITIALIZED, FAILED
    }

    private final boolean[] initializetionStatuses = new boolean[State.values().length - 3];

    private LoadCallback callback;
    private AcceptsOneWidget panel;
    private EventBus eventBus;    

    public ApplicationDataLoader() {        
        setApplicationInitialized(false);
    }

    public void doLoadOnce(LoadCallback callback, AcceptsOneWidget panel, EventBus eventBus) {        
        this.callback = callback;
        this.panel = panel;
        this.eventBus = eventBus;                
        
        if (isApplicationInitiazed())
            processState(State.INITIALIZED);
        else {
            panel.setWidget(new LoadingWidget());
            processState(State.NONE);
        }
    }
    
    public void doLoad(LoadCallback callback, AcceptsOneWidget panel, EventBus eventBus) {
        this.callback = callback;
        this.panel = panel;
        this.eventBus = eventBus;  
        
        Globals.clear();
        setApplicationInitialized(false);
        
        panel.setWidget(new LoadingWidget());
        processState(State.NONE);
    }

    private void processState(State state) {
        switch (state) {
            case NONE:
                loadUser();
                break;

            case USER_LOADED:
                setApplicationInitiazed(State.USER_LOADED, true);

                loadHelpdesks();
                break;

            case HELPDESKS_LOADED:
                setApplicationInitiazed(State.HELPDESKS_LOADED, true);

                // paralel load
                loadHelpdeskRoles();                
                loadHelpdeskFilters();                
                loadHelpdeskCategories();
                loadHelpdeskMembers();
                break;

            case HELPDESK_ROLES_LOADED:
                setApplicationInitiazed(State.HELPDESK_ROLES_LOADED, true);
                break;

            case HELPDESK_FILTERS_LOADED:
                setApplicationInitiazed(State.HELPDESK_FILTERS_LOADED, true);
                break;

            case HELPDESK_CATEGORIES_LOADED:
                setApplicationInitiazed(State.HELPDESK_CATEGORIES_LOADED, true);
                break;
                
            case HELPDESK_MEMBERS_LOADED:
                setApplicationInitiazed(State.HELPDESK_MEMBERS_LOADED, true);
                
                loadHelpdeskMemberUser();
                loadHelpdeskMemberThreadMembers();
                break;
                
            case HELPDESK_MEMBER_USER_LOADED:
                setApplicationInitiazed(State.HELPDESK_MEMBER_USER_LOADED, true);
                break;
                
            case HELPDESK_MEMBER_THREAD_MEMBERS_LOADED:
                setApplicationInitiazed(State.HELPDESK_MEMBER_THREAD_MEMBERS_LOADED, true);
                
                loadThreadMemberThread();
                break;
            
            case THREAD_MEMBER_THREAD_LOADED:
                setApplicationInitiazed(State.THREAD_MEMBER_THREAD_LOADED, true);
                break;

            case FAILED:
                initializationFailed();
                break;

            case INITIALIZED:
                initializationComplete();
                break;

            default:
                throw new IllegalArgumentException(state.toString());
        }

        if (state != State.FAILED && state != State.INITIALIZED && state != State.NONE && isApplicationInitiazed())
            processState(State.INITIALIZED);
    }

    private boolean isApplicationInitiazed() {
        for (boolean initiazed : initializetionStatuses) {
            if (!initiazed)
                return false;
        }

        return true;
    }

    private void setApplicationInitialized(boolean value) {
        for (int i = 0; i < initializetionStatuses.length; i++)
            initializetionStatuses[i] = value;
    }

    private void setApplicationInitiazed(State state, boolean value) {
        initializetionStatuses[state.ordinal()] = value;
    }

    private void loadUser() {
        if (Globals.getCurrentUser() != null && Globals.getCurrentUser().getId().equals(CookieUtils.getCookieUserId()))
            initUser(Globals.getCurrentUser());
        
        else 
            new UserRestApi().get(CookieUtils.getCookieUserId(), new RestDomainCallback<User>() {
                @Override
                public void onDomainSuccess(User domain) {
                    initUser(domain);
                }
            });           
    }

    private void initUser(User user) {
        Globals.setCurrentUser(user);
        CookieUtils.setCookieUser(user);

        if (user != null)
            processState(State.USER_LOADED);
        else
            processState(State.FAILED);
    }

    private void loadHelpdesks() {
        new HelpdeskRestApi().listForUser(Globals.getCurrentUser().getId(), new RestDomainListCallback<Helpdesk>() {
            @Override
            public void onDomainListSuccess(Collection<Helpdesk> entities) {
                initHelpdesks(entities);
            }
        });
    }

    private void initHelpdesks(Collection<Helpdesk> helpdesks) {
        Globals.setHelpdesks(helpdesks);
        processState(State.HELPDESKS_LOADED);
    }

    private void loadHelpdeskRoles() {        
        Iterator<Helpdesk> iterator = Globals.getHelpdesks().iterator();
        doLoadHelpdeskRoles(iterator);
    }
    
    private void doLoadHelpdeskRoles(Iterator<Helpdesk> iterator) {
        if (iterator.hasNext()) {
            final Helpdesk helpdesk = iterator.next();

            Globals.setHelpdeskRolesFor(helpdesk, Arrays.asList(HelpdeskRole.values()));
            
            doLoadHelpdeskRoles(iterator);
           
        } else
            processState(State.HELPDESK_ROLES_LOADED);
    }
    
    private void loadHelpdeskFilters() {
        Iterator<Helpdesk> iterator = Globals.getHelpdesks().iterator();
        doLoadHelpdeskFilters(iterator);
    }

    private void doLoadHelpdeskFilters(final Iterator<Helpdesk> iterator) {
        if (iterator.hasNext()) {
            final Helpdesk helpdesk = iterator.next();

            new FilterRestApi().listForHelpdesk(helpdesk.getId(), new RestDomainListCallback<Filter>() {
                @Override
                public void onDomainListSuccess(Collection<Filter> entities) {
                    initHelpdeskFilters(helpdesk, entities);
                    doLoadHelpdeskFilters(iterator);
                }
            });
            
        } else
            processState(State.HELPDESK_FILTERS_LOADED);
    }
    
    private void initHelpdeskFilters(Helpdesk helpdesk, Collection<Filter> filters) {
        Globals.setFilterMapFor(helpdesk, Globals.asMap(filters));        
    }
    
    private void loadHelpdeskCategories() {
        Iterator<Helpdesk> iterator = Globals.getHelpdesks().iterator();
        doLoadHelpdeskCategories(iterator);
    }

    private void doLoadHelpdeskCategories(final Iterator<Helpdesk> iterator) {
        if (iterator.hasNext()) {
            final Helpdesk helpdesk = iterator.next();

            new CategoryRestApi().listForHelpdesk(helpdesk.getId(), new RestDomainListCallback<Category>() {
                @Override
                public void onDomainListSuccess(Collection<Category> entities) {
                    initHelpdeskCategories(helpdesk, entities);
                    doLoadHelpdeskCategories(iterator);
                }
            });
            
        } else
            processState(State.HELPDESK_CATEGORIES_LOADED);

    }

    private void initHelpdeskCategories(Helpdesk helpdesk, Collection<Category> categories) {
        Globals.setCategoryMapFor(helpdesk, Globals.asMap(categories));
    }

    private void loadHelpdeskMembers() {
        Iterator<Helpdesk> iterator = Globals.getHelpdesks().iterator();
        doLoadHelpdeskMembers(iterator);
    }

    private void doLoadHelpdeskMembers(final Iterator<Helpdesk> iterator) {
        if (iterator.hasNext()) {
            final Helpdesk helpdesk = iterator.next();
            
           // TODO Loading all - thats bad !!!
            /*if (Globals.isHelpdeskOwner(Globals.getCurrentUser(), helpdesk))
                new HelpdeskMemberRestApi().listForHelpdesk(helpdesk.getId(), new RestDomainListCallback<HelpdeskMember>() {
                    @Override
                    public void onDomainListSuccess(Collection<HelpdeskMember> entities) {
                        initHelpdeskMembers(helpdesk, entities);
                        doLoadHelpdeskMembers(iterator);
                    }
                });
            
            else 
                new HelpdeskMemberRestApi().getForUserAndHelpdesk(Globals.getCurrentUser().getId(), helpdesk.getId(), new RestDomainCallback<HelpdeskMember>() {
                    public void onDomainSuccess(HelpdeskMember domain) {
                        initHelpdeskMembers(helpdesk, Arrays.asList(domain));
                        doLoadHelpdeskMembers(iterator);
                    }
                });*/
            
            new HelpdeskMemberRestApi().listForHelpdesk(helpdesk.getId(), new RestDomainListCallback<HelpdeskMember>() {
                @Override
                public void onDomainListSuccess(Collection<HelpdeskMember> entities) {
                    initHelpdeskMembers(helpdesk, entities);
                    doLoadHelpdeskMembers(iterator);
                }
            });
            
        } else
            processState(State.HELPDESK_MEMBERS_LOADED);
    }

    private void initHelpdeskMembers(Helpdesk helpdesk, Collection<HelpdeskMember> helpdeskMembers) {
        ensureHelpdeskMemberRoles(helpdeskMembers);
        Globals.setHelpdeskMemberMapFor(helpdesk, Globals.asMap(helpdeskMembers));
    }    
    
    private void loadHelpdeskMemberUser() {
        Set<HelpdeskMember> helpdeskMembers = new HashSet<HelpdeskMember>();
        Map<Long, User> users = new HashMap<Long, User>();
        
        users.put(Globals.getCurrentUser().getId(), Globals.getCurrentUser());
        
        for (Helpdesk helpdesk : Globals.getHelpdesks()) {
            Map<Long, HelpdeskMember> map = Globals.getHelpdeskMemberMapFor(helpdesk);
            helpdeskMembers.addAll(map.values());            
        }
        
        Iterator<HelpdeskMember> iterator = helpdeskMembers.iterator();
        doLoadHelpdeskMemberUser(iterator, users);
    }
    
    private void doLoadHelpdeskMemberUser(final Iterator<HelpdeskMember> iterator, final Map<Long, User> users) {
        if (iterator.hasNext()) {
            final HelpdeskMember helpdeskMember = iterator.next();
            
            if (users.containsKey(helpdeskMember.getUserId())) {
                initHelpdeskMemberUser(helpdeskMember, users.get(helpdeskMember.getUserId()));
                doLoadHelpdeskMemberUser(iterator, users);
            }
            
            else 
                new UserRestApi().get(helpdeskMember.getUserId(), new RestDomainCallback<User>() {
                    @Override
                    public void onDomainSuccess(User domain) {
                        initHelpdeskMemberUser(helpdeskMember, domain);
                        users.put(domain.getId(), domain);
                        doLoadHelpdeskMemberUser(iterator, users);
                    }
                });
        } else 
            processState(State.HELPDESK_MEMBER_USER_LOADED);
    }
    
    private void initHelpdeskMemberUser(HelpdeskMember helpdeskMember, User user) {
        Globals.setUserFor(helpdeskMember, user);
    }
    
    private void loadHelpdeskMemberThreadMembers() {
        Set<HelpdeskMember> helpdeskMembers = new HashSet<HelpdeskMember>();
        
        for (Helpdesk helpdesk : Globals.getHelpdesks())
            for (HelpdeskMember helpdeskMember : Globals.getHelpdeskMemberMapFor(helpdesk).values())
                if (helpdeskMember.getUserId().equals(Globals.getCurrentUser().getId()))                
                    helpdeskMembers.add(helpdeskMember);                                
        
        Iterator<HelpdeskMember> iterator = helpdeskMembers.iterator();      
        doLoadHelpdeskMemberThreadMembers(iterator);
    }
    
    private void doLoadHelpdeskMemberThreadMembers(final Iterator<HelpdeskMember> iterator) {
        if (iterator.hasNext()) {
            final HelpdeskMember helpdeskMember = iterator.next();
            
            new ThreadMemberRestApi().listForHelpdeskMember(helpdeskMember.getId(), new RestDomainListCallback<ThreadMember>() {
                @Override
                public void onDomainListSuccess(Collection<ThreadMember> entities) {
                    initHelpdeskMemberThreadMembers(helpdeskMember, entities);
                    doLoadHelpdeskMemberThreadMembers(iterator);
                }
            });
        } else
            processState(State.HELPDESK_MEMBER_THREAD_MEMBERS_LOADED);
        
    }
    
    private void initHelpdeskMemberThreadMembers(HelpdeskMember helpdeskMember, Collection<ThreadMember> threadMembers) {
        Map<Long, ThreadMember> map = Globals.asMap(threadMembers);
        Globals.setThreadMemberMapFor(helpdeskMember, map);
    }
    
    private void loadThreadMemberThread() {
        Set<ThreadMember> threadMembers = new HashSet<ThreadMember>();
        Map<Long, Thread> threads = new HashMap<Long, Thread>();
        
        for (Helpdesk helpdesk : Globals.getHelpdesks())
            for (HelpdeskMember helpdeskMember : Globals.getHelpdeskMemberMapFor(helpdesk).values()) {
                if (helpdeskMember.getUserId().equals(Globals.getCurrentUser().getId())) {
                    Map<Long, ThreadMember> map = Globals.getThreadMemberMapFor(helpdeskMember);
                    threadMembers.addAll(map.values());
                }                
            }
        
        Iterator<ThreadMember> iterator = threadMembers.iterator();
        doLoadThreadMemberThread(iterator, threads);
    }
    
    private void doLoadThreadMemberThread(final Iterator<ThreadMember> iterator, final Map<Long, Thread> threads) {
        if (iterator.hasNext()) {
            final ThreadMember threadMember = iterator.next();
            
            if (threads.containsKey(threadMember.getThreadId())) {
                initThreadMemberThread(threadMember, threads.get(threadMember.getThreadId()));
                doLoadThreadMemberThread(iterator, threads);
                
            } else {             
                new ThreadRestApi().get(threadMember.getThreadId(), new RestDomainCallback<Thread>() {
                    @Override
                    public void onDomainSuccess(Thread domain) {
                        initThreadMemberThread(threadMember, domain);
                        threads.put(domain.getId(), domain);
                        doLoadThreadMemberThread(iterator, threads);
                    }
                });
            }
            
        } else 
            processState(State.THREAD_MEMBER_THREAD_LOADED);
    }
    
    private void initThreadMemberThread(ThreadMember threadMember, Thread thread) {
        Globals.setThreadFor(threadMember, thread);
    }
    
    private void ensureHelpdeskMemberRoles(Collection<HelpdeskMember> helpdeskMembers) {
        if (helpdeskMembers == null)
            return;
        
        for (HelpdeskMember helpdeskMember : helpdeskMembers) {
            List<Object> helpdeskRolesObj = new LinkedList<Object>();
            List<HelpdeskRole> helpdeskRoles = new LinkedList<HelpdeskRole>();
            
            helpdeskRolesObj.addAll(helpdeskMember.getHelpdeskRoles());
            
            for (Object helpdeskRoleObj : helpdeskRolesObj) {
                HelpdeskRole role = HelpdeskRole.valueOf(helpdeskRoleObj.toString());
                helpdeskRoles.add(role);
            }
            
            helpdeskMember.setHelpdeskRoles(helpdeskRoles);
        }
    }

    private void initializationComplete() {
        setApplicationInitialized(true);        

        if (callback != null)
            callback.onLoadComplete(panel, eventBus);
    }

    private void initializationFailed() {
        setApplicationInitialized(false);
        Globals.clear();

        if (callback != null)
            callback.onLoadFailed(panel, eventBus);
    }        
}
